--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
meshchatx/src/backend/web_audio_bridge.py b960db2e8bf87b96c14f6421402a16798d7a9ded (b960db2e) Text, 15.75 KB
T8b949e# SPDX-License-Identifier: 0BSD
Tff7b72import T7ee787asyncio
Tff7b72import T7ee787contextlib
Tff7b72import T7ee787json
Tff7b72import T7ee787threading
Tff7b72import T7ee787numpy Tff7b72as T7ee787np
Tff7b72import T7ee787RNS
Tff7b72from T7ee787LXSTT7ee787.T7ee787Codecs Tff7b72import Te6edf3NullTb4b4b4, Te6edf3Raw
Tff7b72from T7ee787LXSTT7ee787.T7ee787Mixer Tff7b72import Te6edf3Mixer
Tff7b72from T7ee787LXSTT7ee787.T7ee787Pipeline Tff7b72import Te6edf3Pipeline
Tff7b72from T7ee787LXSTT7ee787.T7ee787Sinks Tff7b72import Te6edf3LocalSink
Tff7b72from T7ee787LXSTT7ee787.T7ee787Sources Tff7b72import Te6edf3LocalSource
Tff7b72from T7ee787.T7ee787telephone_manager Tff7b72import Te6edf3Tee
Te6edf3_HOSTLESS_AUDIO_INSTALLED Tff7b72= Tff7b72False
Te6edf3_ORIG_LINE_SOURCE Tff7b72= Tff7b72None
Te6edf3_ORIG_LINE_SINK Tff7b72= Tff7b72None
Tff7b72def Td2a8ff_log_debugTb4b4b4(Te6edf3msgTb4b4b4: Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Te6edf3msgTb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_DEBUGTb4b4b4)
Tff7b72class T56d364HostlessAudioSourceTb4b4b4(Te6edf3LocalSourceTb4b4b4)Tb4b4b4:
T8b949e"""LineSource stand-in that never opens PulseAudio / host capture devices.
Docker headless and other web deployments have no usable host mic. LXST still
constructs LineSource during call setup before the websocket bridge attaches.
This class accepts the LineSource constructor shape and later yields to
WebAudioSource once a browser client connects.
"""
Tff7b72def Tff7b72__init__Tb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3preferred_deviceTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3target_frame_msTff7b72=T79c0ff60Tb4b4b4,
Te6edf3codecTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3sinkTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3filtersTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3gainTff7b72=T79c0ff0.0Tb4b4b4,
Te6edf3ease_inTff7b72=T79c0ff0.0Tb4b4b4,
Te6edf3skipTff7b72=T79c0ff0.0Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffpreferred_device Tff7b72= Te6edf3preferred_device
Tff7b72selfTff7b72.Td2a8fftarget_frame_ms Tff7b72= Te6edf3target_frame_ms Tff7b72or T79c0ff60
Tff7b72selfTff7b72.Td2a8ffsink Tff7b72= Te6edf3sink
Tff7b72selfTff7b72.Td2a8fffilters Tff7b72= Te6edf3filters
Tff7b72selfTff7b72.Td2a8ffgain Tff7b72= Te6edf3gain
Tff7b72selfTff7b72.Td2a8ffease_in Tff7b72= Te6edf3ease_in
Tff7b72selfTff7b72.Td2a8ffskip Tff7b72= Te6edf3skip
Tff7b72selfTff7b72.Td2a8ffcodec Tff7b72= Te6edf3codec Tff7b72or Te6edf3RawTb4b4b4(Te6edf3channelsTff7b72=T79c0ff1Tb4b4b4, Te6edf3bitdepthTff7b72=T79c0ff16Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffchannels Tff7b72= T79c0ff1
Tff7b72selfTff7b72.Td2a8ffsamplerate Tff7b72= T79c0ff48000
Tff7b72selfTff7b72.Td2a8ffbitdepth Tff7b72= T79c0ff16
Tff7b72selfTff7b72.Td2a8ffshould_run Tff7b72= Tff7b72False
Tff7b72selfTff7b72.Td2a8ffsamples_per_frame Tff7b72= Tffa657maxTb4b4b4(
T79c0ff1Tb4b4b4,
Tffa657intTb4b4b4(Tffa657roundTb4b4b4(Tb4b4b4(Tff7b72selfTff7b72.Td2a8fftarget_frame_ms Tff7b72/ T79c0ff1000.0Tb4b4b4) Tff7b72* Tff7b72selfTff7b72.Td2a8ffsamplerateTb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffstartTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffshould_run Tff7b72= Tff7b72True
Tff7b72def Td2a8ffstopTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffshould_run Tff7b72= Tff7b72False
Tff7b72def Td2a8ffcan_receiveTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3from_sourceTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72True
Tff7b72def Td2a8ffhandle_frameTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3frameTb4b4b4, Te6edf3sourceTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72pass
Tff7b72def Td2a8ffpush_pcmTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3pcm_bytesTb4b4b4: Tffa657bytesTb4b4b4)Tb4b4b4:
T8b949e# Used if a bridge client attaches before WebAudioSource swap.
Tff7b72if Tff7b72not Te6edf3pcm_bytes Tff7b72or Tff7b72not Tff7b72selfTff7b72.Td2a8ffsinkTb4b4b4:
Tff7b72return
Tff7b72tryTb4b4b4:
Te6edf3samples Tff7b72= Tb4b4b4(
Te6edf3npTff7b72.Td2a8fffrombufferTb4b4b4(Te6edf3pcm_bytesTb4b4b4, Te6edf3dtypeTff7b72=Te6edf3npTff7b72.Td2a8ffint16Tb4b4b4)Tff7b72.Td2a8ffastypeTb4b4b4(Te6edf3npTff7b72.Td2a8fffloat32Tb4b4b4) Tff7b72/ T79c0ff32768.0
Tb4b4b4)
Tff7b72if Te6edf3samplesTff7b72.Td2a8ffsize Tff7b72== T79c0ff0Tb4b4b4:
Tff7b72return
Te6edf3samples Tff7b72= Te6edf3samplesTff7b72.Td2a8ffreshapeTb4b4b4(Tff7b72-T79c0ff1Tb4b4b4, T79c0ff1Tb4b4b4)
Te6edf3frame Tff7b72= Te6edf3RawTb4b4b4(Te6edf3channelsTff7b72=T79c0ff1Tb4b4b4, Te6edf3bitdepthTff7b72=T79c0ff16Tb4b4b4)Tff7b72.Td2a8ffencodeTb4b4b4(Te6edf3samplesTb4b4b4)
Tff7b72if Tff7b72selfTff7b72.Td2a8ffsinkTff7b72.Td2a8ffcan_receiveTb4b4b4(Te6edf3from_sourceTff7b72=Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffsinkTff7b72.Td2a8ffhandle_frameTb4b4b4(Te6edf3frameTb4b4b4, Tff7b72selfTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3excTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffHostlessAudioSource: push_pcm failed: Tffd700{Te6edf3excTffd700}Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)
Tff7b72class T56d364HostlessAudioSinkTb4b4b4(Te6edf3LocalSinkTb4b4b4)Tb4b4b4:
T8b949e"""LineSink stand-in that discards PCM when no host speaker exists."""
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3preferred_deviceTff7b72=Tff7b72NoneTb4b4b4, Te6edf3autodigestTff7b72=Tff7b72TrueTb4b4b4, Te6edf3low_latencyTff7b72=Tff7b72FalseTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffpreferred_device Tff7b72= Te6edf3preferred_device
Tff7b72selfTff7b72.Td2a8ffautodigest Tff7b72= Te6edf3autodigest
Tff7b72selfTff7b72.Td2a8fflow_latency Tff7b72= Te6edf3low_latency
Tff7b72selfTff7b72.Td2a8ffshould_run Tff7b72= Tff7b72False
Tff7b72selfTff7b72.Td2a8ffsamplerate Tff7b72= T79c0ff48000
Tff7b72selfTff7b72.Td2a8ffchannels Tff7b72= T79c0ff1
Tff7b72selfTff7b72.Td2a8ff_wants_low_latency Tff7b72= Tff7b72False
Tff7b72def Td2a8ffcan_receiveTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3from_sourceTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72True
Tff7b72def Td2a8ffhandle_frameTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3frameTb4b4b4, Te6edf3sourceTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72pass
Tff7b72def Td2a8ffstartTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffshould_run Tff7b72= Tff7b72True
Tff7b72def Td2a8ffstopTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffshould_run Tff7b72= Tff7b72False
Tff7b72def Td2a8ffenable_low_latencyTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_wants_low_latency Tff7b72= Tff7b72True
Tff7b72def Td2a8ffinstall_hostless_lxst_audioTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657boolTb4b4b4:
T8b949e"""Replace LXST LineSource/LineSink with hostless stand-ins (idempotent)."""
Tff7b72global Te6edf3_HOSTLESS_AUDIO_INSTALLEDTb4b4b4, Te6edf3_ORIG_LINE_SOURCETb4b4b4, Te6edf3_ORIG_LINE_SINK
Tff7b72if Te6edf3_HOSTLESS_AUDIO_INSTALLEDTb4b4b4:
Tff7b72return Tff7b72True
Tff7b72tryTb4b4b4:
Tff7b72from T7ee787LXSTT7ee787.T7ee787Primitives Tff7b72import Te6edf3Telephony Tff7b72as Te6edf3lxst_telephony
Tff7b72except Tf85149Exception Tff7b72as Te6edf3excTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffWebAudioBridge: cannot install hostless audio backends: Tffd700{Te6edf3excTffd700}Ta5d6ff"Tb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4,
Tb4b4b4)
Tff7b72return Tff7b72False
Te6edf3_ORIG_LINE_SOURCE Tff7b72= Tffa657getattrTb4b4b4(Te6edf3lxst_telephonyTb4b4b4, Ta5d6ff"Ta5d6ffLineSourceTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3_ORIG_LINE_SINK Tff7b72= Tffa657getattrTb4b4b4(Te6edf3lxst_telephonyTb4b4b4, Ta5d6ff"Ta5d6ffLineSinkTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3lxst_telephonyTff7b72.Td2a8ffLineSource Tff7b72= Te6edf3HostlessAudioSource
Te6edf3lxst_telephonyTff7b72.Td2a8ffLineSink Tff7b72= Te6edf3HostlessAudioSink
Te6edf3_HOSTLESS_AUDIO_INSTALLED Tff7b72= Tff7b72True
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(
Ta5d6ff"Ta5d6ffWebAudioBridge: installed hostless LXST audio backends (no PulseAudio)Ta5d6ff"Tb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffLOG_INFOTb4b4b4,
Tb4b4b4)
Tff7b72return Tff7b72True
Tff7b72def Td2a8ffhostless_lxst_audio_installedTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657boolTb4b4b4:
Tff7b72return Te6edf3_HOSTLESS_AUDIO_INSTALLED
Tff7b72def Td2a8ffreset_hostless_lxst_audio_for_testsTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
T8b949e"""Restore original LXST LineSource/LineSink (tests only)."""
Tff7b72global Te6edf3_HOSTLESS_AUDIO_INSTALLEDTb4b4b4, Te6edf3_ORIG_LINE_SOURCETb4b4b4, Te6edf3_ORIG_LINE_SINK
Tff7b72if Tff7b72not Te6edf3_HOSTLESS_AUDIO_INSTALLEDTb4b4b4:
Tff7b72return
Tff7b72with Te6edf3contextlibTff7b72.Td2a8ffsuppressTb4b4b4(Tf85149ExceptionTb4b4b4)Tb4b4b4:
Tff7b72from T7ee787LXSTT7ee787.T7ee787Primitives Tff7b72import Te6edf3Telephony Tff7b72as Te6edf3lxst_telephony
Tff7b72if Te6edf3_ORIG_LINE_SOURCE Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3lxst_telephonyTff7b72.Td2a8ffLineSource Tff7b72= Te6edf3_ORIG_LINE_SOURCE
Tff7b72if Te6edf3_ORIG_LINE_SINK Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3lxst_telephonyTff7b72.Td2a8ffLineSink Tff7b72= Te6edf3_ORIG_LINE_SINK
Te6edf3_HOSTLESS_AUDIO_INSTALLED Tff7b72= Tff7b72False
Te6edf3_ORIG_LINE_SOURCE Tff7b72= Tff7b72None
Te6edf3_ORIG_LINE_SINK Tff7b72= Tff7b72None
Tff7b72class T56d364WebAudioSourceTb4b4b4(Te6edf3LocalSourceTb4b4b4)Tb4b4b4:
T8b949e"""Injects PCM frames (int16 little-endian) received over websocket into the transmit mixer."""
T8b949e# ~2.7s of 48 kHz mono int16; normal frames are tens of ms.
Te6edf3MAX_PCM_BYTES Tff7b72= T79c0ff256 Tff7b72* T79c0ff1024
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3target_frame_msTb4b4b4: Tffa657intTb4b4b4, Te6edf3sinkTb4b4b4: Te6edf3MixerTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8fftarget_frame_ms Tff7b72= Te6edf3target_frame_ms Tff7b72or T79c0ff60
Tff7b72selfTff7b72.Td2a8ffsink Tff7b72= Te6edf3sink
Tff7b72selfTff7b72.Td2a8ffcodec Tff7b72= Te6edf3RawTb4b4b4(Te6edf3channelsTff7b72=T79c0ff1Tb4b4b4, Te6edf3bitdepthTff7b72=T79c0ff16Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffchannels Tff7b72= T79c0ff1
Tff7b72selfTff7b72.Td2a8ffsamplerate Tff7b72= T79c0ff48000
Tff7b72selfTff7b72.Td2a8ffbitdepth Tff7b72= T79c0ff16
Tff7b72def Td2a8ffstartTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e# Nothing to start, as frames are pushed from the websocket thread.
Tff7b72pass
Tff7b72def Td2a8ffstopTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e# Nothing to stop, but kept for interface compatibility.
Tff7b72pass
Tff7b72def Td2a8ffcan_receiveTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3from_sourceTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72True
Tff7b72def Td2a8ffhandle_frameTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3frameTb4b4b4, Te6edf3sourceTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
T8b949e# Not used, as frames are pushed via push_pcm.
Tff7b72pass
Tff7b72def Td2a8ffpush_pcmTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3pcm_bytesTb4b4b4: Tffa657bytesTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72if Te6edf3pcm_bytes Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return
Tff7b72if Tffa657lenTb4b4b4(Te6edf3pcm_bytesTb4b4b4) Tff7b72> Tff7b72selfTff7b72.Td2a8ffMAX_PCM_BYTESTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffWebAudioSource: dropping oversized pcm frame (Tffd700{Tffa657lenTb4b4b4(Te6edf3pcm_bytesTb4b4b4)Tffd700}Ta5d6ff bytes)Ta5d6ff"Tb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffLOG_WARNINGTb4b4b4,
Tb4b4b4)
Tff7b72return
Te6edf3samples Tff7b72= Tb4b4b4(
Te6edf3npTff7b72.Td2a8fffrombufferTb4b4b4(Te6edf3pcm_bytesTb4b4b4, Te6edf3dtypeTff7b72=Te6edf3npTff7b72.Td2a8ffint16Tb4b4b4)Tff7b72.Td2a8ffastypeTb4b4b4(Te6edf3npTff7b72.Td2a8fffloat32Tb4b4b4) Tff7b72/ T79c0ff32768.0
Tb4b4b4)
Tff7b72if Te6edf3samplesTff7b72.Td2a8ffsize Tff7b72== T79c0ff0Tb4b4b4:
Tff7b72return
Te6edf3samples Tff7b72= Te6edf3samplesTff7b72.Td2a8ffreshapeTb4b4b4(Tff7b72-T79c0ff1Tb4b4b4, T79c0ff1Tb4b4b4)
Te6edf3frame Tff7b72= Tff7b72selfTff7b72.Td2a8ffcodecTff7b72.Td2a8ffencodeTb4b4b4(Te6edf3samplesTb4b4b4)
Tff7b72if Tff7b72selfTff7b72.Td2a8ffsink Tff7b72and Tff7b72selfTff7b72.Td2a8ffsinkTff7b72.Td2a8ffcan_receiveTb4b4b4(Te6edf3from_sourceTff7b72=Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffsinkTff7b72.Td2a8ffhandle_frameTb4b4b4(Te6edf3frameTb4b4b4, Tff7b72selfTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3excTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffWebAudioSource: failed to push pcm: Tffd700{Te6edf3excTffd700}Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)
Tff7b72class T56d364WebAudioSinkTb4b4b4(Te6edf3LocalSinkTb4b4b4)Tb4b4b4:
T8b949e"""Pushes received PCM frames to websocket clients."""
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3loopTb4b4b4: Te6edf3asyncioTff7b72.Td2a8ffAbstractEventLoopTb4b4b4, Te6edf3send_bytesTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffloop Tff7b72= Te6edf3loop
Tff7b72selfTff7b72.Td2a8ffsend_bytes Tff7b72= Te6edf3send_bytes
Tff7b72def Td2a8ffcan_receiveTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3from_sourceTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72True
Tff7b72def Td2a8ffhandle_frameTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3frameTb4b4b4, Te6edf3sourceTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
T8b949e# frame is expected to be numpy float PCM from receive mixer
Tff7b72if Tffa657hasattrTb4b4b4(Te6edf3frameTb4b4b4, Ta5d6ff"Ta5d6ffastypeTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3samples Tff7b72= Te6edf3npTff7b72.Td2a8ffclipTb4b4b4(Te6edf3frameTb4b4b4, Tff7b72-T79c0ff1.0Tb4b4b4, T79c0ff1.0Tb4b4b4)Tff7b72.Td2a8ffastypeTb4b4b4(Te6edf3npTff7b72.Td2a8fffloat32Tb4b4b4)
Te6edf3pcm Tff7b72= Tb4b4b4(Te6edf3samples Tff7b72* T79c0ff32767.0Tb4b4b4)Tff7b72.Td2a8ffastypeTb4b4b4(Te6edf3npTff7b72.Td2a8ffint16Tb4b4b4)Tff7b72.Td2a8fftobytesTb4b4b4(Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3pcm Tff7b72= Te6edf3frame
Tff7b72selfTff7b72.Td2a8ffloopTff7b72.Td2a8ffcall_soon_threadsafeTb4b4b4(Te6edf3asyncioTff7b72.Td2a8ffcreate_taskTb4b4b4, Tff7b72selfTff7b72.Td2a8ffsend_bytesTb4b4b4(Te6edf3pcmTb4b4b4)Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3excTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffWebAudioSink: failed to handle frame: Tffd700{Te6edf3excTffd700}Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)
Tff7b72class T56d364WebAudioBridgeTb4b4b4:
T8b949e"""Coordinates websocket audio transport with an active LXST telephone call."""
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3telephone_managerTb4b4b4, Te6edf3config_managerTb4b4b4, Te6edf3force_enabledTb4b4b4: Tffa657bool Tff7b72= Tff7b72FalseTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8fftelephone_manager Tff7b72= Te6edf3telephone_manager
Tff7b72selfTff7b72.Td2a8ffconfig_manager Tff7b72= Te6edf3config_manager
Tff7b72selfTff7b72.Td2a8ffforce_enabled Tff7b72= Tffa657boolTb4b4b4(Te6edf3force_enabledTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffclients Tff7b72= Tffa657setTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fftx_sourceTb4b4b4: Te6edf3WebAudioSource Tff7b72| Tff7b72None Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffrx_sinkTb4b4b4: Te6edf3WebAudioSink Tff7b72| Tff7b72None Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffrx_teeTb4b4b4: Te6edf3Tee Tff7b72| Tff7b72None Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ff_loop Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8fflock Tff7b72= Te6edf3threadingTff7b72.Td2a8ffLockTb4b4b4(Tb4b4b4)
Tf0883e@property
Tff7b72def Td2a8ffloopTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ff_loopTb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ff_loop
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_loop Tff7b72= Te6edf3asyncioTff7b72.Td2a8ffget_running_loopTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149RuntimeErrorTb4b4b4:
T8b949e# Fallback to finding it via AsyncUtils if possible
Tff7b72from T7ee787.T7ee787async_utils Tff7b72import Te6edf3AsyncUtils
Tff7b72selfTff7b72.Td2a8ff_loop Tff7b72= Te6edf3AsyncUtilsTff7b72.Td2a8ffmain_loop
Tff7b72return Tff7b72selfTff7b72.Td2a8ff_loop
Tff7b72def Td2a8ff_teleTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72return Tffa657getattrTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephone_managerTb4b4b4, Ta5d6ff"Ta5d6fftelephoneTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72def Td2a8ffconfig_enabledTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ffforce_enabledTb4b4b4:
Tff7b72return Tff7b72True
Tff7b72return Tb4b4b4(
Tff7b72selfTff7b72.Td2a8ffconfig_manager
Tff7b72and Tffa657hasattrTb4b4b4(Tff7b72selfTff7b72.Td2a8ffconfig_managerTb4b4b4, Ta5d6ff"Ta5d6fftelephone_web_audio_enabledTa5d6ff"Tb4b4b4)
Tff7b72and Tff7b72selfTff7b72.Td2a8ffconfig_managerTff7b72.Td2a8fftelephone_web_audio_enabledTff7b72.Td2a8ffgetTb4b4b4(Tb4b4b4)
Tb4b4b4)
Tff7b72def Td2a8ffallow_fallbackTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ffforce_enabledTb4b4b4:
T8b949e# Never restore PulseAudio host paths on headless/web-required hosts.
Tff7b72return Tff7b72False
Tff7b72return Tb4b4b4(
Tff7b72selfTff7b72.Td2a8ffconfig_manager
Tff7b72and Tffa657hasattrTb4b4b4(Tff7b72selfTff7b72.Td2a8ffconfig_managerTb4b4b4, Ta5d6ff"Ta5d6fftelephone_web_audio_allow_fallbackTa5d6ff"Tb4b4b4)
Tff7b72and Tff7b72selfTff7b72.Td2a8ffconfig_managerTff7b72.Td2a8fftelephone_web_audio_allow_fallbackTff7b72.Td2a8ffgetTb4b4b4(Tb4b4b4)
Tb4b4b4)
Tff7b72def Td2a8ffget_diagnosticsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Return a small status snapshot for /api/v1/telephone/status."""
Te6edf3tele Tff7b72= Tff7b72selfTff7b72.Td2a8ff_teleTb4b4b4(Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffforce_enabledTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffforce_enabledTb4b4b4,
Ta5d6ff"Ta5d6ffconfig_enabledTa5d6ff"Tb4b4b4: Tffa657boolTb4b4b4(Tff7b72selfTff7b72.Td2a8ffconfig_enabledTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffhostless_backendsTa5d6ff"Tb4b4b4: Te6edf3hostless_lxst_audio_installedTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffclient_countTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Tff7b72selfTff7b72.Td2a8ffclientsTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_tx_sourceTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8fftx_source Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6ffhas_rx_sinkTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffrx_sink Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6ffhas_active_callTa5d6ff"Tb4b4b4: Tffa657boolTb4b4b4(Te6edf3tele Tff7b72and Tffa657getattrTb4b4b4(Te6edf3teleTb4b4b4, Ta5d6ff"Ta5d6ffactive_callTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ffattach_clientTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3clientTb4b4b4)Tb4b4b4:
Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Te6edf3tele Tff7b72= Tff7b72selfTff7b72.Td2a8ff_teleTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3tele Tff7b72or Tff7b72not Te6edf3teleTff7b72.Td2a8ffactive_callTb4b4b4:
Tff7b72return Tff7b72False
T8b949e# Do not attach during voicemail
Tff7b72if Tffa657getattrTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephone_managerTb4b4b4, Ta5d6ff"Ta5d6ffis_voicemail_session_activeTa5d6ff"Tb4b4b4, Tff7b72FalseTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72False
Tff7b72selfTff7b72.Td2a8ffclientsTff7b72.Td2a8ffaddTb4b4b4(Te6edf3clientTb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_ensure_remote_txTb4b4b4(Te6edf3teleTb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_ensure_rx_teeTb4b4b4(Te6edf3teleTb4b4b4)
Tff7b72return Tff7b72True
Tff7b72def Td2a8ffdetach_clientTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3clientTb4b4b4)Tb4b4b4:
Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Tff7b72if Te6edf3client Tff7b72in Tff7b72selfTff7b72.Td2a8ffclientsTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffclientsTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3clientTb4b4b4)
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffclients Tff7b72and Tff7b72selfTff7b72.Td2a8ffallow_fallbackTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_restore_host_audioTb4b4b4(Tb4b4b4)
Tff7b72async Tff7b72def Td2a8ffsend_statusTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3clientTb4b4b4)Tb4b4b4:
Te6edf3tele Tff7b72= Tff7b72selfTff7b72.Td2a8ff_teleTb4b4b4(Tb4b4b4)
Te6edf3frame_ms Tff7b72= Tffa657getattrTb4b4b4(Te6edf3teleTb4b4b4, Ta5d6ff"Ta5d6fftarget_frame_time_msTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4) Tff7b72or T79c0ff60
Tff7b72await Te6edf3clientTff7b72.Td2a8ffsend_strTb4b4b4(
Te6edf3jsonTff7b72.Td2a8ffdumpsTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6fftypeTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffweb_audio.readyTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffframe_msTa5d6ff"Tb4b4b4: Te6edf3frame_msTb4b4b4,
Ta5d6ff"Ta5d6ffrequiredTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffforce_enabledTb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffpush_client_frameTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3pcm_bytesTb4b4b4: Tffa657bytesTb4b4b4)Tb4b4b4:
Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Te6edf3tele Tff7b72= Tff7b72selfTff7b72.Td2a8ff_teleTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3tele Tff7b72or Tff7b72not Tffa657getattrTb4b4b4(Te6edf3teleTb4b4b4, Ta5d6ff"Ta5d6ffactive_callTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72return
Tff7b72if Tffa657getattrTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephone_managerTb4b4b4, Ta5d6ff"Ta5d6ffis_voicemail_session_activeTa5d6ff"Tb4b4b4, Tff7b72FalseTb4b4b4)Tb4b4b4:
Tff7b72return
T8b949e# Do not feed PCM while mic is muted or half-duplex PTT is released.
Tff7b72if Tffa657getattrTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephone_managerTb4b4b4, Ta5d6ff"Ta5d6fftransmit_mutedTa5d6ff"Tb4b4b4, Tff7b72FalseTb4b4b4) Tff7b72is Tff7b72TrueTb4b4b4:
Tff7b72return
Te6edf3is_squelched Tff7b72= Tffa657getattrTb4b4b4(
Tff7b72selfTff7b72.Td2a8fftelephone_managerTb4b4b4,
Ta5d6ff"Ta5d6ffis_transmit_squelchedTa5d6ff"Tb4b4b4,
Tff7b72NoneTb4b4b4,
Tb4b4b4)
Tff7b72if Tffa657callableTb4b4b4(Te6edf3is_squelchedTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72if Te6edf3is_squelchedTb4b4b4(Tb4b4b4) Tff7b72is Tff7b72TrueTb4b4b4:
Tff7b72return
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8fftx_sourceTb4b4b4:
T8b949e# Hostless LineSource stand-in still accepts PCM until swap.
Te6edf3audio_in Tff7b72= Tffa657getattrTb4b4b4(Te6edf3teleTb4b4b4, Ta5d6ff"Ta5d6ffaudio_inputTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72if Te6edf3audio_in Tff7b72is Tff7b72not Tff7b72None Tff7b72and Tffa657hasattrTb4b4b4(Te6edf3audio_inTb4b4b4, Ta5d6ff"Ta5d6ffpush_pcmTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3audio_inTff7b72.Td2a8ffpush_pcmTb4b4b4(Te6edf3pcm_bytesTb4b4b4)
Tff7b72return
Tff7b72selfTff7b72.Td2a8fftx_sourceTff7b72.Td2a8ffpush_pcmTb4b4b4(Te6edf3pcm_bytesTb4b4b4)
Tff7b72async Tff7b72def Td2a8ff_send_bytes_to_allTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3pcm_bytesTb4b4b4: Tffa657bytesTb4b4b4)Tb4b4b4:
Te6edf3stale Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3ws Tff7b72in Tffa657listTb4b4b4(Tff7b72selfTff7b72.Td2a8ffclientsTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72await Te6edf3wsTff7b72.Td2a8ffsend_bytesTb4b4b4(Te6edf3pcm_bytesTb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Te6edf3staleTff7b72.Td2a8ffappendTb4b4b4(Te6edf3wsTb4b4b4)
Tff7b72for Te6edf3ws Tff7b72in Te6edf3staleTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffdetach_clientTb4b4b4(Te6edf3wsTb4b4b4)
Tff7b72def Td2a8ff_ensure_remote_txTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3teleTb4b4b4)Tb4b4b4:
T8b949e# Rebuild transmit path with websocket-backed source
Tff7b72if Tff7b72selfTff7b72.Td2a8fftx_sourceTb4b4b4:
Tff7b72return
T8b949e# Do not create transmit source during voicemail
Tff7b72if Tffa657getattrTb4b4b4(Tff7b72selfTff7b72.Td2a8fftelephone_managerTb4b4b4, Ta5d6ff"Ta5d6ffis_voicemail_session_activeTa5d6ff"Tb4b4b4, Tff7b72FalseTb4b4b4)Tb4b4b4:
Tff7b72return
Tff7b72tryTb4b4b4:
Tff7b72if Tffa657hasattrTb4b4b4(Te6edf3teleTb4b4b4, Ta5d6ff"Ta5d6ffaudio_inputTa5d6ff"Tb4b4b4) Tff7b72and Te6edf3teleTff7b72.Td2a8ffaudio_inputTb4b4b4:
Te6edf3teleTff7b72.Td2a8ffaudio_inputTff7b72.Td2a8ffstopTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fftx_source Tff7b72= Te6edf3WebAudioSourceTb4b4b4(
Te6edf3target_frame_msTff7b72=Tffa657getattrTb4b4b4(Te6edf3teleTb4b4b4, Ta5d6ff"Ta5d6fftarget_frame_time_msTa5d6ff"Tb4b4b4, T79c0ff60Tb4b4b4)Tb4b4b4,
Te6edf3sinkTff7b72=Te6edf3teleTff7b72.Td2a8fftransmit_mixerTb4b4b4,
Tb4b4b4)
Te6edf3teleTff7b72.Td2a8ffaudio_input Tff7b72= Tff7b72selfTff7b72.Td2a8fftx_source
Tff7b72if Te6edf3teleTff7b72.Td2a8fftransmit_mixer Tff7b72and Tff7b72not Te6edf3teleTff7b72.Td2a8fftransmit_mixerTff7b72.Td2a8ffshould_runTb4b4b4:
Te6edf3teleTff7b72.Td2a8fftransmit_mixerTff7b72.Td2a8ffstartTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3excTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffWebAudioBridge: failed to swap transmit path: Tffd700{Te6edf3excTffd700}Ta5d6ff"Tb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ff_ensure_rx_teeTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3teleTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ffrx_sinkTb4b4b4:
Tff7b72return
Tff7b72tryTb4b4b4:
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffloopTb4b4b4:
Tff7b72return
Te6edf3send_fn Tff7b72= Tff7b72lambda Te6edf3pcmTb4b4b4: Tff7b72selfTff7b72.Td2a8ff_send_bytes_to_allTb4b4b4(Te6edf3pcmTb4b4b4) T8b949e# noqa: E731
Tff7b72selfTff7b72.Td2a8ffrx_sink Tff7b72= Te6edf3WebAudioSinkTb4b4b4(Tff7b72selfTff7b72.Td2a8ffloopTb4b4b4, Te6edf3send_fnTb4b4b4)
T8b949e# Build tee with existing audio_output as first sink to preserve speaker
Te6edf3base_sink Tff7b72= Te6edf3teleTff7b72.Td2a8ffaudio_output
Tff7b72selfTff7b72.Td2a8ffrx_tee Tff7b72= Te6edf3TeeTb4b4b4(Te6edf3base_sinkTb4b4b4) Tff7b72if Te6edf3base_sink Tff7b72else Te6edf3TeeTb4b4b4(Tff7b72selfTff7b72.Td2a8ffrx_sinkTb4b4b4)
Tff7b72if Te6edf3base_sinkTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffrx_teeTff7b72.Td2a8ffadd_sinkTb4b4b4(Tff7b72selfTff7b72.Td2a8ffrx_sinkTb4b4b4)
Te6edf3teleTff7b72.Td2a8ffaudio_output Tff7b72= Tff7b72selfTff7b72.Td2a8ffrx_tee
Tff7b72if Te6edf3teleTff7b72.Td2a8ffreceive_pipelineTb4b4b4:
Te6edf3teleTff7b72.Td2a8ffreceive_pipelineTff7b72.Td2a8ffstopTb4b4b4(Tb4b4b4)
Te6edf3teleTff7b72.Td2a8ffreceive_pipeline Tff7b72= Te6edf3PipelineTb4b4b4(
Te6edf3sourceTff7b72=Te6edf3teleTff7b72.Td2a8ffreceive_mixerTb4b4b4,
Te6edf3codecTff7b72=Te6edf3NullTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3sinkTff7b72=Tff7b72selfTff7b72.Td2a8ffrx_teeTb4b4b4,
Tb4b4b4)
Te6edf3teleTff7b72.Td2a8ffreceive_pipelineTff7b72.Td2a8ffstartTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3excTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffWebAudioBridge: failed to tee receive path: Tffd700{Te6edf3excTffd700}Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)
Tff7b72def Td2a8ff_restore_host_audioTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3tele Tff7b72= Tff7b72selfTff7b72.Td2a8ff_teleTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3teleTb4b4b4:
Tff7b72return
Tff7b72with Te6edf3contextlibTff7b72.Td2a8ffsuppressTb4b4b4(Tf85149ExceptionTb4b4b4)Tb4b4b4:
Tff7b72with Te6edf3contextlibTff7b72.Td2a8ffsuppressTb4b4b4(Tf85149ExceptionTb4b4b4)Tb4b4b4:
Tff7b72if Tffa657hasattrTb4b4b4(Te6edf3teleTb4b4b4, Ta5d6ff"Ta5d6ff_Telephony__reconfigure_transmit_pipelineTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3teleTff7b72.Td2a8ff_Telephony__reconfigure_transmit_pipelineTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3teleTff7b72.Td2a8ffreceive_pipelineTb4b4b4:
Te6edf3teleTff7b72.Td2a8ffreceive_pipelineTff7b72.Td2a8ffstopTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3teleTff7b72.Td2a8ffaudio_output Tff7b72and Tff7b72selfTff7b72.Td2a8ffrx_teeTb4b4b4:
T8b949e# If tee had original sink as first element, revert
Te6edf3primary Tff7b72= Tff7b72selfTff7b72.Td2a8ffrx_teeTff7b72.Td2a8ffsinksTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72if Tff7b72selfTff7b72.Td2a8ffrx_teeTff7b72.Td2a8ffsinks Tff7b72else Tff7b72None
Tff7b72if Te6edf3primary Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3teleTff7b72.Td2a8ffaudio_output Tff7b72= Te6edf3primary
Tff7b72if Te6edf3teleTff7b72.Td2a8ffreceive_mixerTb4b4b4:
Te6edf3teleTff7b72.Td2a8ffreceive_pipeline Tff7b72= Te6edf3PipelineTb4b4b4(
Te6edf3sourceTff7b72=Te6edf3teleTff7b72.Td2a8ffreceive_mixerTb4b4b4,
Te6edf3codecTff7b72=Te6edf3NullTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3sinkTff7b72=Te6edf3teleTff7b72.Td2a8ffaudio_outputTb4b4b4,
Tb4b4b4)
Te6edf3teleTff7b72.Td2a8ffreceive_pipelineTff7b72.Td2a8ffstartTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fftx_source Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffrx_sink Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffrx_tee Tff7b72= Tff7b72None
Tff7b72def Td2a8ffon_call_endedTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffclientsTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fftx_source Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffrx_sink Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffrx_tee Tff7b72= Tff7b72None
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────